﻿Namespace TheiaImageing_v1

    ''' <summary>Represents a color-value of a pixel in different color spaces.</summary>
    ''' <remarks>Implementations use doubles as internal representation for high precisions.</remarks>
    Public Interface IPixel

        ''' <summary>.NET representation of the pixels RGB value</summary>
        ''' <remarks>
        ''' Avoid setting the color with this property. Values are internally represented as doubles. 
        ''' Using <see cref="Drawing.Color"/> may influence the high precision through cut-offs.</remarks>
        Property RGB() As Drawing.Color

        ''' <summary>Red channel of RGB color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Red() As Double

        ''' <summary>Green channel of RGB color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Green() As Double

        ''' <summary>Blue channel of RGB color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Blue() As Double

        ''' <summary>Hue of HSV color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Hue() As Double

        ''' <summary>Saturation of HSV color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Saturation() As Double

        ''' <summary>Value of HSV color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Value() As Double

        ''' <summary>Brightness, based on wheighted RGB-Values</summary>
        ''' <returns>Value between 0 (black) and 1 (white)</returns>
        ReadOnly Property Brightness() As Double

        ''' <summary>Luminance of CIE L*ab color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Luminance() As Double

        ''' <summary>a dimension of CIE L*ab color space</summary>
        Property a() As Double

        ''' <summary>b dimension of CIE L*ab color space</summary>
        Property b() As Double

        ''' <summary>u dimension of CIE L*uv color space</summary>
        Property u() As Double

        ''' <summary>v dimension of CIE L*uv color space</summary>
        Property v() As Double

        ''' <summary>Chroma of CIE L*CH° color space</summary>
        ReadOnly Property Chroma() As Double

        ''' <summary>Hue° of L*CH° color space</summary>
        ReadOnly Property HueAngle() As Double

        ''' <summary>X dimension in XYZ color space</summary>
        ''' <value>Value between 0 and 0.95047</value><returns>Value between 0 and 0.95047</returns>
        ''' <remarks>Values less than 0 or higher than 0.95047 will be truncated</remarks>
        Property X() As Double

        ''' <summary>Y dimension in XYZ color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Y() As Double

        ''' <summary>Z dimension in XYZ color space</summary>
        ''' <value>Value between 0 and 1.08883</value><returns>Value between 0 and 1.08883</returns>
        ''' <remarks>Values less than 0 or higher than 1.08883 will be truncated</remarks>
        Property Z() As Double

        ''' <summary>Cyan channel of CMY color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Cyan() As Double

        ''' <summary>Magenta channel of CMY color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Magenta() As Double

        ''' <summary>Yellow channel of CMY color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property Yellow() As Double

        ''' <summary>Cyan channel of CMYK color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property KCyan() As Double

        ''' <summary>Magenta channel of CMYK color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property KMagenta() As Double

        ''' <summary>Yellow channel of CMYK color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property KYellow() As Double

        ''' <summary>Key plate (black) of CMYK color space</summary>
        ''' <value>Value between 0 and 1</value><returns>Value between 0 and 1</returns>
        ''' <remarks>Values less than 0 or higher than 1 will be truncated</remarks>
        Property KeyPlate() As Double

    End Interface

    Public Class PixelRGB
        Implements IPixel

        Protected Const refX = 0.95047
        Protected Const refY = 1.0
        Protected Const refZ = 1.08883

        Protected _R As Double
        Protected _G As Double
        Protected _B As Double

        Public Sub New()
            Me.New(0, 0, 0)
        End Sub

        Public Sub New(ByVal RGBValue As Drawing.Color)
            Me.New(CDbl(RGBValue.R) / Byte.MaxValue, CDbl(RGBValue.G) / Byte.MaxValue, CDbl(RGBValue.B) / Byte.MaxValue)
        End Sub

        Public Sub New(ByVal R As Double, ByVal G As Double, ByVal B As Double)
            _R = Math.Max(Math.Min(R, 1), 0)
            _G = Math.Max(Math.Min(G, 1), 0)
            _B = Math.Max(Math.Min(B, 1), 0)
        End Sub

        Public Property Luminance() As Double Implements IPixel.Luminance
            Get
                Dim vY As Double = Y / refY
                If (vY > 0.008856) Then vY = vY ^ (1 / 3) Else vY = (7.787 * vY) + (16 / 116)
                Return (116 * vY) - 16
            End Get
            Set(ByVal value As Double)
                Dim vY As Double = (value + 16) / 116
                If (vY ^ 3 > 0.008856) Then vY = vY ^ 3 Else vY = (vY - 16 / 116) / 7.787
                Y = refY * vY
            End Set
        End Property

        Public Property a() As Double Implements IPixel.a
            Get
                Dim vX As Double = X '/ refX
                Dim vY As Double = Y '/ refY
                If (vX > 0.008856) Then vX = vX ^ (1 / 3) Else vX = (7.787 * vX) + (16 / 116)
                If (vY > 0.008856) Then vY = vY ^ (1 / 3) Else vY = (7.787 * vY) + (16 / 116)
                Return 500 * (vX - vY)
            End Get
            Set(ByVal value As Double)
                Dim vX = value / 500 + ((Luminance + 16) / 116)
                If (vX ^ 3 > 0.008856) Then vX = vX ^ 3 Else vX = (7.787 * vX) + (16 / 116)
                X = vX '* refX 
            End Set
        End Property

        Public Property b() As Double Implements IPixel.b
            Get
                Dim vY As Double = Y '/ refY
                Dim vZ As Double = Z '/ refZ
                If (vY > 0.008856) Then vY = vY ^ (1 / 3) Else vY = (7.787 * vY) + (16 / 116)
                If (vZ > 0.008856) Then vZ = vZ ^ (1 / 3) Else vZ = (7.787 * vZ) + (16 / 116)
                Return 200 * (vY - vZ)
            End Get
            Set(ByVal value As Double)
                Dim vZ = ((Luminance + 16) / 116) - value / 200
                If (vZ ^ 3 > 0.008856) Then vZ = vZ ^ 3 Else vZ = (7.787 * vZ) + (16 / 116)
                Z = vZ '* refZ 
            End Set
        End Property

        Public Property u() As Double Implements IPixel.u
            Get
                Dim vU As Double = (4 * X) / (X + (15 * Y) + (3 * Z))
                Dim refU = (4 * refX) / (refX + (15 * refY) + (3 * refZ))

                Return 13 * Luminance * (vU - refU)
            End Get
            Set(ByVal value As Double)
                Dim vL As Double = Luminance
                Dim vU = value / (13 * vL) + (4 * refX) / (refX + (15 * refY) + (3 * refZ))
                Dim vV = v / (13 * vL) + (9 * refY) / (refX + (15 * refY) + (3 * refZ))

                Dim vY As Double = Y
                Dim vX As Double = -(9 * vY * vU) / ((vU - 4) * vV - vU * vV)
                Dim vZ As Double = (9 * vY - (15 * vV * vY) - (vV * X)) / (3 * vV)

                SetXYZ(vX, vY, vZ)
            End Set
        End Property

        Public Property v() As Double Implements IPixel.v
            Get
                Dim vV As Double = (9 * Y) / (X + (15 * Y) + (3 * Z))
                Dim refV = (9 * refY) / (refX + (15 * refY) + (3 * refZ))

                Return 13 * Luminance * (vV - refV)
            End Get
            Set(ByVal value As Double)
                Dim vL As Double = Luminance
                Dim vU = u / (13 * vL) + (4 * refX) / (refX + (15 * refY) + (3 * refZ))
                Dim vV = value / (13 * vL) + (9 * refY) / (refX + (15 * refY) + (3 * refZ))

                Dim vY As Double = Y
                Dim vX As Double = -(9 * vY * vU) / ((vU - 4) * vV - vU * vV)
                Dim vZ As Double = (9 * vY - (15 * vV * vY) - (vV * X)) / (3 * vV)

                SetXYZ(vX, vY, vZ)
            End Set
        End Property

        Public ReadOnly Property Chroma() As Double Implements IPixel.Chroma
            Get
                Dim vX As Double = X
                Dim vY As Double = Y
                Dim vZ As Double = Z
                If (vX > 0.008856) Then vX = vX ^ (1 / 3) Else vX = (7.787 * vX) + (16 / 116)
                If (vY > 0.008856) Then vY = vY ^ (1 / 3) Else vY = (7.787 * vY) + (16 / 116)
                If (vZ > 0.008856) Then vZ = vZ ^ (1 / 3) Else vZ = (7.787 * vZ) + (16 / 116)

                Return Math.Sqrt((500 * (vX - vY)) ^ 2 + (200 * (vY - vZ)) ^ 2)
            End Get
        End Property

        Public ReadOnly Property HueAngle() As Double Implements IPixel.HueAngle
            Get
                Dim vX As Double = X
                Dim vY As Double = Y
                Dim vZ As Double = Z
                If (vX > 0.008856) Then vX = vX ^ (1 / 3) Else vX = (7.787 * vX) + (16 / 116)
                If (vY > 0.008856) Then vY = vY ^ (1 / 3) Else vY = (7.787 * vY) + (16 / 116)
                If (vZ > 0.008856) Then vZ = vZ ^ (1 / 3) Else vZ = (7.787 * vZ) + (16 / 116)

                Dim vH = Math.Atan2((200 * (vY - vZ)), (500 * (vX - vY)))
                If (vH > 0) Then vH = (vH / Math.PI) * 180 Else vH = 360 - (Math.Abs(vH) / Math.PI) * 180
                Return vH
            End Get
        End Property

        Public ReadOnly Property Brightness() As Double Implements IPixel.Brightness
            Get
                'Return _R * 0.299 + _G * 0.587 + _B * 0.114
                Return Math.Sqrt(0.241 * _R ^ 2 + 0.691 * _G ^ 2 + 0.068 * _B ^ 2)
            End Get
        End Property

        Public Property Red() As Double Implements IPixel.Red
            Get
                Return _R
            End Get
            Set(ByVal value As Double)
                _R = Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        Public Property Green() As Double Implements IPixel.Green
            Get
                Return _G
            End Get
            Set(ByVal value As Double)
                _G = Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        Public Property Blue() As Double Implements IPixel.Blue
            Get
                Return _B
            End Get
            Set(ByVal value As Double)
                _B = Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        Public Property RGB() As System.Drawing.Color Implements IPixel.RGB
            Get
                Return System.Drawing.Color.FromArgb(Math.Max(Math.Min(_R, 1), 0) * Byte.MaxValue, Math.Max(Math.Min(_G, 1), 0) * Byte.MaxValue, Math.Max(Math.Min(_B, 1), 0) * Byte.MaxValue)
            End Get
            Set(ByVal value As System.Drawing.Color)
                _R = CDbl(value.R) / Byte.MaxValue
                _G = CDbl(value.G) / Byte.MaxValue
                _B = CDbl(value.B) / Byte.MaxValue
            End Set
        End Property

        Public Property Cyan() As Double Implements IPixel.Cyan
            Get
                Return 1 - _R
            End Get
            Set(ByVal value As Double)
                _R = 1 - Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        Public Property Magenta() As Double Implements IPixel.Magenta
            Get
                Return 1 - _G
            End Get
            Set(ByVal value As Double)
                _G = 1 - Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        Public Property Yellow() As Double Implements IPixel.Yellow
            Get
                Return 1 - b
            End Get
            Set(ByVal value As Double)
                _B = 1 - Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        Public Property Hue() As Double Implements IPixel.Hue
            Get
                Dim vMax As Double = Math.Max(_R, Math.Max(_G, _B))
                Dim dMinMax As Double = vMax - Math.Min(_R, Math.Min(_G, _B))

                Dim vHue As Double

                If (dMinMax = 0) Then
                    vHue = 0
                Else
                    Dim dR As Double = (((vMax - _R) / 6) + (dMinMax / 2)) / dMinMax
                    Dim dG As Double = (((vMax - _G) / 6) + (dMinMax / 2)) / dMinMax
                    Dim dB As Double = (((vMax - _B) / 6) + (dMinMax / 2)) / dMinMax

                    If (_R = vMax) Then : vHue = dB - dG
                    ElseIf (_G = vMax) Then : vHue = (1 / 3) + dR - dB
                    ElseIf (_B = vMax) Then : vHue = (2 / 3) + dG - dB
                    End If

                    If (vHue < 0) Then vHue += 1
                    If (vHue > 1) Then vHue -= 1
                End If

                Return vHue
            End Get
            Set(ByVal value As Double)
                Dim vS As Double = Saturation

                If vS > 0 Then
                    Dim vH As Double = value * 6
                    Dim vV As Double = Me.Value

                    If (vH >= 6) Then vH = 0
                    Dim i As Integer = Math.Floor(vH)
                    Dim v1 As Double = vV * (1 - vS)
                    Dim v2 As Double = vV * (1 - vS * (vH - i))
                    Dim v3 As Double = vV * (1 - vS * (1 - (vH - i)))
                    If (i = 0) Then
                        _R = vV : _G = v3 : _B = v1
                    ElseIf (i = 1) Then
                        _R = v2 : _G = vV : _B = v1
                    ElseIf (i = 2) Then
                        _R = v1 : _G = vV : _B = v3
                    ElseIf (i = 3) Then
                        _R = v1 : _G = v2 : _B = vV
                    ElseIf (i = 4) Then
                        _R = v3 : _G = v1 : _B = vV
                    Else
                        _R = vV : _G = v1 : _B = v2
                    End If
                End If
            End Set
        End Property

        Public Property Saturation() As Double Implements IPixel.Saturation
            Get
                Dim vMax As Double = Math.Max(_R, Math.Max(_G, _B))
                Dim dMinMax As Double = vMax - Math.Min(_R, Math.Min(_G, _B))

                If (dMinMax = 0) Then
                    Return 0
                Else
                    Return dMinMax / vMax
                End If
            End Get
            Set(ByVal value As Double)

            End Set
        End Property

        Public Property Value() As Double Implements IPixel.Value
            Get
                Return Math.Max(_R, Math.Max(_G, _B))
            End Get
            Set(ByVal value As Double)

            End Set
        End Property

        ''' <summary>Help-Function to set X, Y and Z Property</summary>
        ''' <param name="ValX">New X value</param>
        ''' <param name="ValY">New Y value</param>
        ''' <param name="ValZ">New Z value</param>
        ''' <remarks></remarks>
        Private Sub SetXYZ(ByVal ValX As Double, ByVal ValY As Double, ByVal ValZ As Double)
            Dim vX = Math.Max(Math.Min(ValX, refX), 0)
            Dim vY = Math.Max(Math.Min(ValY, refY), 0)
            Dim vZ = Math.Max(Math.Min(ValZ, refZ), 0)

            Dim vR = vX * 3.2406 + vY * -1.5372 + vZ * -0.4986
            Dim vG = vX * -0.9689 + vY * 1.8758 + vZ * 0.0415
            Dim vB = vX * 0.0557 + vY * -0.204 + vZ * 1.057

            If vR > 0.0031308 Then _R = 1.055 * (vR ^ (1 / 2.4)) - 0.055 Else _R = vR * 12.92
            If vG > 0.0031308 Then _G = 1.055 * (vG ^ (1 / 2.4)) - 0.055 Else _G = vG * 12.92
            If vB > 0.0031308 Then _B = 1.055 * (vB ^ (1 / 2.4)) - 0.055 Else _B = vB * 12.92
        End Sub

        Public Property X() As Double Implements IPixel.X
            Get
                Dim vR As Double
                Dim vG As Double
                Dim vB As Double

                If (_R > 0.04045) Then vR = ((_R + 0.055) / 1.055) ^ 2.4 Else vR = _R / 12.92
                If (_G > 0.04045) Then vG = ((_G + 0.055) / 1.055) ^ 2.4 Else vG = _G / 12.92
                If (_B > 0.04045) Then vB = ((_B + 0.055) / 1.055) ^ 2.4 Else vB = _B / 12.92

                Return vR * 0.4124 + vG * 0.3576 + vB * 0.1805
            End Get
            Set(ByVal value As Double)
                SetXYZ(value, Y, Z)
            End Set
        End Property

        Public Property Y() As Double Implements IPixel.Y
            Get
                Dim vR As Double
                Dim vG As Double
                Dim vB As Double

                If (_R > 0.04045) Then vR = ((_R + 0.055) / 1.055) ^ 2.4 Else vR = _R / 12.92
                If (_G > 0.04045) Then vG = ((_G + 0.055) / 1.055) ^ 2.4 Else vG = _G / 12.92
                If (_B > 0.04045) Then vB = ((_B + 0.055) / 1.055) ^ 2.4 Else vB = _B / 12.92

                Return vR * 0.2126 + vG * 0.7152 + vB * 0.0722
            End Get
            Set(ByVal value As Double)
                SetXYZ(X, value, Z)
            End Set
        End Property

        Public Property Z() As Double Implements IPixel.Z
            Get
                Dim vR As Double
                Dim vG As Double
                Dim vB As Double

                If (_R > 0.04045) Then vR = ((_R + 0.055) / 1.055) ^ 2.4 Else vR = _R / 12.92
                If (_G > 0.04045) Then vG = ((_G + 0.055) / 1.055) ^ 2.4 Else vG = _G / 12.92
                If (_B > 0.04045) Then vB = ((_B + 0.055) / 1.055) ^ 2.4 Else vB = _B / 12.92

                Return vR * 0.0193 + vG * 0.1192 + vB * 0.9505
            End Get
            Set(ByVal value As Double)
                SetXYZ(X, Y, value)
            End Set
        End Property

        Public Property KCyan() As Double Implements IPixel.KCyan
            Get
                Return (1 - _R) - KeyPlate
            End Get
            Set(ByVal value As Double)
                _R = 1 - (Math.Max(Math.Min(value, 1 - KeyPlate), 0) * (1 - KeyPlate) + KeyPlate)
            End Set
        End Property

        Public Property KMagenta() As Double Implements IPixel.KMagenta
            Get
                Return (1 - _G) - KeyPlate
            End Get
            Set(ByVal value As Double)
                _G = 1 - (Math.Max(Math.Min(value, 1 - KeyPlate), 0) * (1 - KeyPlate) + KeyPlate)
            End Set
        End Property

        Public Property KYellow() As Double Implements IPixel.KYellow
            Get
                Return (1 - _B) - KeyPlate
            End Get
            Set(ByVal value As Double)
                _B = 1 - (Math.Max(Math.Min(value, 1 - KeyPlate), 0) * (1 - KeyPlate) + KeyPlate)
            End Set
        End Property

        Public Property KeyPlate() As Double Implements IPixel.KeyPlate
            Get
                Return Math.Min(Math.Min(1 - _R, 1 - _G), 1 - _B)
            End Get
            Set(ByVal value As Double)
                Dim K As Double = Math.Max(Math.Min(value, 1), 0)
                Dim KC As Double = KCyan
                Dim KM As Double = KMagenta
                Dim KY As Double = KYellow

                _R = Math.Max(1 - (KC * (1 - K) + K), 0)
                _G = Math.Max(1 - (KM * (1 - K) + K), 0)
                _B = Math.Max(1 - (KY * (1 - K) + K), 0)
            End Set
        End Property

    End Class

    ''' <summary>Extents <see cref="PixelRGB">PixelRGB</see> class with alpha chanel</summary>
    Public Class PixelRGBA
        Inherits PixelRGB

        Protected _Alpha As Double

        ''' <summary>Level of tranceparency.</summary>
        ''' <value>Value between 0 and 1</value>
        ''' <returns>Value between 0 and 1</returns>
        ''' <remarks>
        ''' <list>
        ''' <item>0 = no tranceparence</item>
        ''' <item>1 = full tranceparency</item>
        ''' </list>
        ''' </remarks>
        Public Property Alpha() As Double
            Get
                Return _Alpha
            End Get
            Set(ByVal value As Double)
                _Alpha = Math.Max(Math.Min(value, 1), 0)
            End Set
        End Property

        ''' <summary>Level of tranceparency.</summary>
        ''' <returns>Value between 0 and 1</returns>
        ''' <remarks>
        ''' <list>
        ''' <item>
        ''' 0 = full tranceparency
        ''' 1 = no tranceparence
        ''' </item>
        ''' </list></remarks>
        ReadOnly Property Opacity() As Double
            Get
                Return 1 - Alpha
            End Get
        End Property

    End Class


End Namespace

